#include "outputdevice.h"
#include <iostream> 
#include <fstream> 
#include <pthread.h>
#include <queue>
#include "uMap.hh"
#include "uSmartPointer.hh"
#include<tr1/memory>

#include<assert.h>

#include <semaphore.h>

#include "task.h"

std::ofstream* out;
uMap<int, uSmartPointer<Task> > task_map;
std::queue<int> unused_ids;


pthread_mutex_t status_lock=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t datastructure_lock;
sem_t tasks_sem;

int status=0;

pthread_t worker_thread;



void print_lib_error() {
        std::cerr << "Output device library error" << std::endl;
}

int print_system_error(int error) {
        if(error==0) return 0;
        
        std::cerr << "system error" << std::endl;
        return -1;
}

/**
 * the thread that actually writes stuff to the file
 * ignores attributes and returns no value.
 */
void* _workerThread(void *attrs) { 
	int error=0;
	while(true) {
		//wait for tasks
		error |= sem_wait(&tasks_sem);
		//start critical section:
		
		//first thing we do is get a task from the cache
		pthread_mutex_lock(&datastructure_lock);
		std::map<int, uSmartPointer<Task> >::iterator it=task_map.begin();
		
		//if the map is empty we should exit the thread.
		if(it==task_map.end()) {
			error |= pthread_mutex_unlock(&datastructure_lock);
			break;
		}
		int task_id = it->first;
		uSmartPointer<Task> task = it->second;
		
		task_map.erase(it); //we have a copy, remove it from the data structure.
		
		unused_ids.push(task_id); //add the id to the list of available ids.
		
		error |= pthread_mutex_unlock(&datastructure_lock);
		//now that all references to this task have been purged from our system we can finish it
		error |= task->writeTask(*out);
		
	}
	
	error |= pthread_mutex_lock(&status_lock);
	
	out->close();
	delete out;
	status=0;
	error |= pthread_mutex_destroy(&datastructure_lock);
	error |= sem_destroy(&tasks_sem);
	
	error |= pthread_mutex_unlock(&status_lock);
	
	print_system_error(error);
	
	return NULL;
}


/**
 * DESCRIPTION: The function creates the file filename if it does not exist and open the file for writing. 
 *              This function should be called prior to any other functions as a necessary precondition for their success. 
 *	
 * RETURN VALUE: On success 0, otherwise -1. 
 */
int initdevice(char *filename) {
	int error=0;
	pthread_mutex_lock(&status_lock);
	
	if(status!=0) {
		print_lib_error();
		return -1;
	}
	
	out=new std::ofstream(filename, std::ios_base::app | std::ios_base::out | std::ios_base::binary);
	if(out->fail()) {
		pthread_mutex_unlock(&status_lock);
		return print_system_error(-1);
	}
	status=1;
	
	error |= pthread_mutex_init(&datastructure_lock, NULL); //always returns zero
	
	error |= sem_init(&tasks_sem, 0, 0);
	
	pthread_create(&worker_thread, NULL, _workerThread, NULL);
	pthread_detach(worker_thread);
	pthread_mutex_unlock(&status_lock);
	return error;
}

/**
 * Generates a new task-id or reuses an existing one. 
 * NOTE: datastructure_lock must be locked before entering this function.
 */
int generateTaskId() {
	int newid;
	
	//generate an initial id if there are no working tasks.
	if(task_map.empty()) {
		while(!unused_ids.empty()) unused_ids.pop();
		return 1;
	}
	
	if(!unused_ids.empty()) {
		newid=unused_ids.front();
		unused_ids.pop();
		return newid;
	}
	
	//if we don't have any unused_ids allocated and there are already tasks create a new id. Since the ids in the map are ordered, we just take the largest id+1.
	std::map<int, uSmartPointer<Task> >::reverse_iterator it = task_map.rbegin();
	assert(it!=task_map.rend()); //this can only happen if the data structure isn't locked.
	int new_id=it->first+1;
	while(task_map.contains(new_id)) new_id++;
	return it->first+1;
}

/**
 * DESCRIPTION: The function writes the input buffer to the file. The buffer may be freed once this call returns. 
 *              You should deal with any memory management issues. 
 * 		Note this is non-blocking package you are required to implement you should return ASAP, 
 * 		even if the buffer has not yet been written to the disk.
 * 
 * RETURN VALUE: On success, the function returns a task_id (>= 0), which identifies this write operation. 
 * 		 Note, you should reuse task_ids when they become available.  On failure, -1 will be returned.
 */
int write2device(char *buffer, int length) {
	int error=0;
	error |= pthread_mutex_lock(&status_lock);
	if(status!=1) {
		print_lib_error();
		return -1;
	}
	error |= pthread_mutex_unlock(&status_lock);
	
	error |= pthread_mutex_lock(&datastructure_lock);
	
	int newid=generateTaskId();
	Task* t= new Task(newid, buffer, length);
	task_map.insert(t->get_id(),uSmartPointer<Task>(t));
	//task_map[t->get_id()] = uSmartPointer<Task>(t);
	
	error |= pthread_mutex_unlock(&datastructure_lock);
	
	error |= sem_post(&tasks_sem);
	
	return error;
}



/**
 * DESCRIPTION: Block until the specified task_id has been written to the file. 
 * 		The task_id is a value that was previously returned by write2device function. 
 * 		In case of error, the function should cause the process to exit.
 *
 */
void flush2device(int task_id) {
	int error = 0;
	
	error |= pthread_mutex_lock(&status_lock);
	if(status!=1) {
		print_lib_error();
	}
	error |= pthread_mutex_unlock(&status_lock);
	
	bool found=false;
	error |= pthread_mutex_lock(&datastructure_lock);
	
	uSmartPointer<Task> task;
	if(task_map.contains(task_id)) {
		task = task_map[task_id];
		found=true;
	}
	error |= pthread_mutex_unlock(&datastructure_lock);
	if(found) error |= task->wait_on_me();
	
	print_system_error(error);
	
	return;
}


/**
 * DESCRIPTION: close the output file and reset the system so that it is possible to call initdevice again. 
 *              All pending task_ids should be written to output disk file.     
 *              Any attempt to write new buffers while the system is shutting down should cause an error. 
 *
 */
void closedevice() {
	int error=0;
	
	error |= pthread_mutex_lock(&status_lock);
	if(status!=1) {
		print_lib_error();
		return;
	}
	
	error |= sem_post(&tasks_sem);
	
	status=2;
	
	error |= pthread_mutex_unlock(&status_lock);
}